package com.zombie.logic.core;

import static com.zombie.Constants.GROUP_COUNT;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;

import org.w3c.dom.Node;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.zombie.Constants;
import com.zombie.Renderable;
import com.zombie.effect.AbstractEffect;
import com.zombie.logic.core.box2d.ContactFilter;
import com.zombie.logic.core.box2d.ContactListener;
import com.zombie.logic.core.box2d.DestructionListener;
import com.zombie.logic.core.box2d.RayCastRequest;
import com.zombie.logic.level.Level;
import com.zombie.logic.object.GameObject;
import com.zombie.state.GameState;
import com.zombie.util.XMLUtils;

public class ZombieWorld {

	public static World world;
	public static ConcurrentHashMap<Integer,GameObject> objects = new ConcurrentHashMap<Integer,GameObject>();
	public static Map<Integer,List<Renderable>> renderGroups = new ConcurrentHashMap<Integer,List<Renderable>>();
	public static List<AbstractEffect> effects = new ArrayList<AbstractEffect>();

	
	public static Level level;
	public static ArrayList<RayCastRequest> raycasts = new ArrayList<RayCastRequest>();
	
	public static void update(float f){
		world.step(1/60f, 1, 1);
		
		for(GameObject obj : objects.values())
			obj.update(f);

       	for(int i = 0; i < effects.size();i++) {
       		effects.get(i).update((int) (f*1000));
    		if (effects.get(i).getLifeTime() <= 0){
    			removeEffect(effects.get(i));
    		}
       	}
		for (int i = 0; i < raycasts.size();i++){
			world.rayCast(raycasts.get(i).callback, raycasts.get(i).start, raycasts.get(i).finish);
			raycasts.remove(raycasts.get(i));
		}
	}	


	public static void init(int id){
		if (world != null)
			world.dispose();
		world = null;
		world = new World(new Vector2(0,0), false);
		world.setContactFilter(new ContactFilter());
		world.setContactListener(new ContactListener());
		world.setDestructionListener(new DestructionListener());
		setupDimension(Constants.MAP_WIDTH, Constants.MAP_HEIGHT);
		createRenderGroups();
		objects.clear();
		effects.clear();
		if (level != null)
			level.dispose();

		loadLevel(id);
		GameState.getInstance().startNewLevel(level);
		System.out.println("ZombieWorld.init() "+world);
	}
	
	public static void setupDimension(float w, float h) {
		BodyDef boxDef = new BodyDef();
		boxDef.type = BodyType.StaticBody;
		Body box = world.createBody(boxDef);
		PolygonShape ps1 = new PolygonShape();
		PolygonShape ps2 = new PolygonShape();
		PolygonShape ps3 = new PolygonShape();
		PolygonShape ps4 = new PolygonShape();
		ps1.setAsBox(16*Constants.WORLD_TO_BOX, h*Constants.WORLD_TO_BOX, new Vector2(-16*Constants.WORLD_TO_BOX,h/2*Constants.WORLD_TO_BOX), 0);
		ps2.setAsBox(16*Constants.WORLD_TO_BOX, h*Constants.WORLD_TO_BOX, new Vector2((16+w)*Constants.WORLD_TO_BOX,h/2*Constants.WORLD_TO_BOX), 0);
		ps3.setAsBox(w*Constants.WORLD_TO_BOX, 16*Constants.WORLD_TO_BOX, new Vector2(w/2*Constants.WORLD_TO_BOX,-16*Constants.WORLD_TO_BOX), 0);
		ps4.setAsBox(w*Constants.WORLD_TO_BOX, 16*Constants.WORLD_TO_BOX, new Vector2(w/2*Constants.WORLD_TO_BOX,(16+h)*Constants.WORLD_TO_BOX), 0);
		box.createFixture(ps1, 0.0f).setRestitution(0.5f);
		box.createFixture(ps2, 0.0f).setRestitution(0.5f);
		box.createFixture(ps3, 0.0f).setRestitution(0.5f);
		box.createFixture(ps4, 0.0f).setRestitution(0.5f);
	}
	
	private static ZombieWorld instance;	
	
	public static ZombieWorld getInstance(){
		if (instance == null)
			instance = new ZombieWorld();
		return instance;
	}

	public static Body createNpcBox(float x, float y, float w, float h){
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.DynamicBody;
		bodyDef.angularDamping = 10f;
		bodyDef.linearDamping = 10f;
		bodyDef.position.set(x*Constants.WORLD_TO_BOX, y*Constants.WORLD_TO_BOX);
		Body b = world.createBody(bodyDef);
//		CircleShape shape = new CircleShape();
		PolygonShape shape = new PolygonShape();
		shape.setAsBox(w/2*Constants.WORLD_TO_BOX, h/2*Constants.WORLD_TO_BOX);

//		shape.setRadius(Math.min(w, h)/2*Constants.WORLD_TO_BOX);
		b.createFixture(shape, 2);
		return b;
	}	
	
	public static Body createNpcCircle(float x, float y, float radius){
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.DynamicBody;
		bodyDef.angularDamping = 10f;
		bodyDef.linearDamping = 10f;
		bodyDef.position.set(x*Constants.WORLD_TO_BOX, y*Constants.WORLD_TO_BOX);
		Body b = world.createBody(bodyDef);
		CircleShape shape = new CircleShape();
		shape.setRadius(radius*Constants.WORLD_TO_BOX);
		b.createFixture(shape, 2);
		return b;
	}
	
	public static Body createStaticBox(float x, float y, float w, float h){
		return createStaticBox(x,y,w,h,0);
	}
	
	public static Body createStaticBox(float x, float y, float w, float h,float angle){
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.StaticBody;
		bodyDef.position.set(x*Constants.WORLD_TO_BOX, y*Constants.WORLD_TO_BOX);
		bodyDef.angle = angle;
		Body b = world.createBody(bodyDef);
		PolygonShape shape = new PolygonShape();
		shape.setAsBox(w/2*Constants.WORLD_TO_BOX, h/2*Constants.WORLD_TO_BOX);
		b.createFixture(shape, 0);
		return b;
	}	
	
	public static Body createStaticPoly(Vector<Vector2> list,float x, float y){
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.StaticBody;
		bodyDef.position.set(x*Constants.WORLD_TO_BOX, y*Constants.WORLD_TO_BOX);
		Body b = world.createBody(bodyDef);
		PolygonShape shape = new PolygonShape();
		shape.set((Vector2[]) list.toArray());
		b.createFixture(shape, 0);
		return b;
	}
	

	public static Body createBulletBox(float x, float y, float w, float h,float angle,float density, float damping){
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.DynamicBody;
		bodyDef.bullet = true;
		bodyDef.angularDamping = damping;
		bodyDef.linearDamping = damping;
		bodyDef.angle = (float) Math.toRadians(angle);
		bodyDef.position.set(x*Constants.WORLD_TO_BOX, y*Constants.WORLD_TO_BOX);
		Body b = world.createBody(bodyDef);
		PolygonShape shape = new PolygonShape();
		shape.setAsBox(w/2*Constants.WORLD_TO_BOX, h/2*Constants.WORLD_TO_BOX);
		b.createFixture(shape, density).setSensor(true);
		return b;
	}
	
	public static Body createDynamicBox(float x, float y, float w, float h,float angle,float density,boolean isSensor, float damping){
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.DynamicBody;
		bodyDef.position.set(x*Constants.WORLD_TO_BOX, y*Constants.WORLD_TO_BOX);
		bodyDef.angle = (float) Math.toRadians(angle);
		bodyDef.angularDamping = damping;
		bodyDef.linearDamping = damping;
		Body b = world.createBody(bodyDef);
		PolygonShape shape = new PolygonShape();
//		shape.setAsBox(w/2, h/2, center, angle)
		shape.setAsBox(w/2*Constants.WORLD_TO_BOX, h/2*Constants.WORLD_TO_BOX);
		b.createFixture(shape, density).setSensor(isSensor);
		return b;
	}

	public static Body createDynamicCircle(float x, float y, float radius,
			float angle, float density, boolean isSensor, float damping) {
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.DynamicBody;
		bodyDef.position.set(x*Constants.WORLD_TO_BOX, y*Constants.WORLD_TO_BOX);
		bodyDef.angle = (float) Math.toRadians(angle);
		bodyDef.angularDamping = damping;
		bodyDef.linearDamping = damping;
		Body b = world.createBody(bodyDef);
		CircleShape shape = new CircleShape();
		shape.setRadius(radius*Constants.WORLD_TO_BOX);
		b.createFixture(shape, density).setSensor(isSensor);
		return b;
	}

	
	private static void loadLevel(int id){
		Node first = XMLUtils.getNodeForStream(Gdx.files.internal("data/data/level.xml").read());
//		Document doc = first.getOwnerDocument();
//		try {
//			File f = Gdx.files.external("level2.xml").file();
//			f.createNewFile();
//			System.out.println(f.getAbsolutePath());
//			FileOutputStream stream = new FileOutputStream(f);
//			XMLUtils.saveXML(doc, stream);
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		
		for(int i = 0; i < first.getChildNodes().getLength();i++ ){
			Node n = first.getChildNodes().item(i);	
			if (n.getNodeName().equalsIgnoreCase("level")){
				if (id == Integer.parseInt(n.getAttributes().getNamedItem("id").getNodeValue()))
					Level.loadLevel(n);
			}
		}
	}

	public static void addEffect(AbstractEffect eff) {
		effects.add(eff);
		addToRenderGroup(eff);
	}

	public static void removeEffect(AbstractEffect eff) {
		eff.remove();
		effects.remove(eff);
		removeFromRenderGroup(eff);
	}

	public static void addObject(GameObject object) {
		objects.putIfAbsent(object.hashCode(), object);
		addToRenderGroup(object);
	}

	public static void removeObject(GameObject object) {
		objects.remove(object.hashCode());
		removeFromRenderGroup(object);
	}
	
	private static void addToRenderGroup(Renderable r){
		if (r == null)
			return;
		if (!renderGroups.containsKey(r.getRenderGroup()))
			renderGroups.put(r.getRenderGroup(), new ArrayList<Renderable>());
		if (!renderGroups.get(r.getRenderGroup()).contains(r))
			renderGroups.get(r.getRenderGroup()).add(r);
	}
	
	private static void removeFromRenderGroup(Renderable r){
		if (r == null)
			return;
		if (renderGroups.containsKey(r.getRenderGroup()))
			renderGroups.get(r.getRenderGroup()).remove(r);
	}
	
	private static void createRenderGroups() {
		renderGroups.clear();
		for(int i = 0; i < GROUP_COUNT;i++)
			renderGroups.put(i, new ArrayList<Renderable>());
	}
	
}
